`timescale 1ps/1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: JerryTech
// 
// Create Date: 2023/04/23
// Design Name: 
// Module Name: CIC_Insert_Filter
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: CIC LPF
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

//Refer to https://www.runoob.com/w3cnote/verilog-cic.html

module cic_integrator # (           //integrator to add 3 stage value
    parameter IN_WIDTH  = 12,
    parameter OUT_WIDTH = 32,
    parameter STAGE     =  3
)
(
    input  wire                             sys_clk,
    input  wire                             sys_rst,
    input  wire signed [IN_WIDTH  - 1:0 ]   data_in,
    input  wire                             enable,
    output wire signed [OUT_WIDTH - 1:0 ]   data_out,
    output wire                             valid
);

reg         [STAGE - 1:0]      enable_reg;
reg signed  [OUT_WIDTH - 1:0 ] adder_reg [0:STAGE - 1];

always @ (posedge sys_clk)   //delay the enable signal for timing
    if(sys_rst)
        enable_reg <= 0;
    else
        enable_reg <= {enable_reg[STAGE - 2:0],enable};

assign valid = enable_reg[STAGE - 1];

genvar i;
generate
    for(i = 0;i < STAGE;i = i + 1)
    begin : plus_gen
        always @ (posedge sys_clk)
            if(sys_rst)
                adder_reg[i] <= 0;
            else if(i==0)
                adder_reg[0] <= adder_reg[0] + data_in;
            else
                adder_reg[i] <= adder_reg[i] + adder_reg[i - 1];
    end
endgenerate

assign data_out = adder_reg[STAGE - 1];

endmodule

module insert #(
    parameter INSERT_D = 4,
    parameter WIDTH    = 32
)
(
    input  wire                             sys_clk,
    input  wire                             sys_rst,
    input  wire signed [WIDTH  - 1:0 ]      data_in,
    input  wire                             enable,
    output reg  signed [WIDTH - 1:0 ]       data_out,
    output reg                              valid
);

reg [10:0] counter;

always @ (posedge sys_clk)
    if(sys_rst)
        counter <= 0;
    else if(enable || counter > 0)
        counter <= counter == INSERT_D - 1 ? 0 : counter + 11'd1;

always @ (posedge sys_clk)
    if(sys_rst)
        data_out <= 0;
    else if(enable)
        data_out <= data_in;
    else
        data_out <= 0;

always @ (posedge sys_clk)
    if(sys_rst)
        valid <= 0;
    else if(enable || counter > 0)
        valid <= 1;
    else
        valid <= 0;

endmodule


module cic_comb #(                 //1 stage FIR.Only use sub,no mult.
    parameter IN_WIDTH  = 32,
    parameter OUT_WIDTH = 12,
    parameter STAGE     =  3
)
(
    input  wire                             sys_clk,
    input  wire                             sys_rst,
    input  wire signed [IN_WIDTH  - 1:0 ]   data_in,
    input  wire                             enable,
    output wire signed [OUT_WIDTH - 1:0 ]   data_out,
    output wire                             valid
);

reg         [IN_WIDTH  - 1:0 ] data_reg     [0:STAGE - 1];
reg         [IN_WIDTH  - 1:0 ] data_reg_dly [0:STAGE - 1];
wire signed [IN_WIDTH  - 1:0 ] data_sub     [0:STAGE - 1];
reg  signed [IN_WIDTH  - 1:0 ] data_out_reg              ;

reg [STAGE - 1:0]      enable_reg;

always @ (posedge sys_clk)
    if(sys_rst)
        enable_reg <= 0;
    else
        enable_reg <= {enable_reg[STAGE - 2:0],enable};

assign valid = enable_reg[STAGE - 1];

genvar i;
generate
    for(i=0;i<STAGE;i=i+1)
        begin : sub_sys
            always @ (posedge sys_clk)
                if(sys_rst)
                    data_reg[i] <= 0;
                else if(enable) begin
                    if(i==0)
                        data_reg[i] <= data_in;
                    else
                        data_reg[i] <= data_sub[i - 1];
                end
                
            always @ (posedge sys_clk)
                if(sys_rst)
                    data_reg_dly[i] <= 0;
                else if(enable)
                    data_reg_dly[i] <= data_reg[i];
            
            assign data_sub[i] = data_reg[i] - data_reg_dly[i];
        end
endgenerate

always @ (posedge sys_clk)
    if(sys_rst)
        data_out_reg <= 0;
    else if(enable)
        data_out_reg <= data_sub[STAGE - 1];

assign data_out = data_out_reg[IN_WIDTH - 1] == 0 ? {1'd0,data_out_reg[IN_WIDTH - 2 :IN_WIDTH - OUT_WIDTH ]} 
                                                  : {1'd1,data_out_reg[IN_WIDTH - 2 :IN_WIDTH - OUT_WIDTH ]};

endmodule

module jerrytech_cic_insert #(
    parameter IN_WIDTH = 12,    //Width for input
    parameter OUT_WIDTH = 24,   //Width for output
    parameter STAGE    = 3,  //Filter stages
    parameter INSERT_D  = 4   //Upgrade sample parameter
)
(
    input  wire                             sys_clk,
    input  wire                             sys_rst,
    input  wire signed [IN_WIDTH  - 1:0 ]   data_in,
    input  wire                             enable,
    output wire signed [OUT_WIDTH - 1:0 ]   data_out,
    output wire                             valid
);

wire signed [IN_WIDTH  - 1:0 ]   inter_data;
wire                                inter_valid;
wire signed [IN_WIDTH  - 1:0 ]   ins_data;
wire                                ins_valid;

cic_comb #(
    .IN_WIDTH  (IN_WIDTH),
    .OUT_WIDTH (IN_WIDTH),
    .STAGE     (STAGE)
) u_comb
(
    .sys_clk  (sys_clk),
    .sys_rst  (sys_rst),
    .data_in  (data_in),
    .enable   (enable),
    .data_out (inter_data),
    .valid    (inter_valid)
);

insert #(
    .WIDTH(IN_WIDTH),
    .INSERT_D(INSERT_D)
) u_insert
(
    .sys_clk  (sys_clk),
    .sys_rst  (sys_rst),
    .data_in  (inter_data),
    .enable   (inter_valid),
    .data_out (ins_data),
    .valid    (ins_valid)
);

cic_integrator # (
    .IN_WIDTH(IN_WIDTH),
    .OUT_WIDTH(OUT_WIDTH),
    .STAGE(STAGE)
) u_intergrator
(
    .sys_clk  (sys_clk),
    .sys_rst  (sys_rst),
    .data_in  (ins_data),
    .enable   (ins_valid ),
    .data_out (data_out),
    .valid    (valid)
);


endmodule
